package net.wanglu.www.spdr.digger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.wanglu.www.spdr.entities.Corpus;
import net.wanglu.www.spdr.filter.NodeFilterFactory;
import net.wanglu.www.spdr.fixer.Fixer;
import net.wanglu.www.spdr.log.LoggerFactory;
import net.wanglu.www.spdr.rule.Rule;

import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.tags.Html;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.SimpleNodeIterator;
/**
 * DiggerTemplate是网页挖掘的一个模板类，依据站点的规则
 * 获取站点的有效链接，对有效链接进行内容挖掘，获取语料组装成
 * 语料对象。
 * @author deadspace
 *
 */
public abstract class DiggerTemplate implements Digger {

	/**
	 * 挖掘内容所在页面的链接被存储在一个Map中，这个静态属
	 * 性定义了该链接在Map中的键名。
	 */
	public static final String FROM = "from";
	/**
	 * 挖掘的内容标题被存储在一个Map中，这个静态属性定义了
	 * 内容标题在Map中的键名。
	 */
	public static final String TITLE = "title";
	/**
	 * 挖掘的内容发布日期被存储在一个Map中，这个静态属性定
	 * 义了发布日期在Map中的键名。
	 */
	public static final String DATE = "date";
	/**
	 * 挖掘的内容摘要被存储在一个Map中，这个静态属性定义了
	 * 内容摘要在Map中的键名。
	 */
	public static final String ABSTRACT = "abstract";
	/**
	 * 挖掘的内容正文被存储在一个Map中，这个静态属性定义了
	 * 内容正文在Map中的键名。
	 */
	public static final String TEXT = "text";
	/**
	 * 挖掘的内容作者被存储在一个Map中，这个静态属性定义了
	 * 内容作者在Map中的键名。
	 */
	public static final String AUTHOR = "author";
	/**
	 * 待挖掘的站点规则，存储在这个Rule中。站点的规则通过
	 * Spring的Setter注入方式初始化到这个属性中。
	 */
	protected Rule rule;
	/**
	 * 依据站点中的各种规则，生成页面的结点过滤器。生成过滤器
	 * 的工作由每个站点自己的过滤器工厂完成。
	 */
	protected NodeFilterFactory nodeFilterFactory;
	/**
	 * 需要对每个站点都抓取下来的内容进行修整。修整的工作由各
	 * 站点的修整器来完成。
	 */
	protected Fixer fixer;
	
	public Fixer getFixer() {
		return fixer;
	}
	public void setFixer(Fixer fixer) {
		this.fixer = fixer;
	}
	
	public NodeFilterFactory getNodeFilterFactory() {
		return nodeFilterFactory;
	}
	public void setNodeFilterFactory(NodeFilterFactory nodeFilterFactory) {
		this.nodeFilterFactory = nodeFilterFactory;
	}
	
	public Rule getRule() {
		return rule;
	}
	public void setRule(Rule rule) {
		this.rule = rule;
	}
	/**
	 * 抓取url指定的页面
	 * @param url
	 * @return 将内容作为一个字符串返回
	 * @throws Exception
	 */
	public String grabPage(String url) throws Exception {
		Parser parser = new Parser(url);
		NodeList list = parser.parse(new NodeClassFilter(Html.class));
		Node node = list.elementAt(0);
		return node.toHtml();
	}
	/**
	 * 依据某个站点的规则，获取该站点首页的有效链接，
	 * 并对有效链接通过url拣选器进一步遴选，剔除敏
	 * 感的和无意义的url。
	 * @param rule Rule对象，表示某个站点的规则
	 * @return 返回一个Set，存有首页上的各个有效链接
	 * @throws Exception
	 */
	protected HashSet<String> validUrls(Rule rule) throws Exception {
		if ( rule == null ) return null;
		
		String homeUrl = rule.homeUrl();
		Parser parser = new Parser(homeUrl);
		NodeFilter nodeFilter = new TagNameFilter("a");
		NodeList nodeList = parser.parse(nodeFilter);
		
		Pattern validPattern = rule.validUrlRule();
		Pattern rejectedPattern = rule.rejectedUrlRule();
		
		HashSet<String> urls = new HashSet<String>(128);
	    for(SimpleNodeIterator e = nodeList.elements(); e.hasMoreNodes();){
	    	LinkTag linkNode = (LinkTag)e.nextNode();
	    	String href = linkNode.extractLink();
	    	Matcher validMatcher = validPattern.matcher(href);
	    	Matcher rejectedMatcher = rejectedPattern.matcher(href);
	    	if ( validMatcher.find() && !rejectedMatcher.find()) {
	    		urls.add(href);
	    	}
	    }
	    
	    return urls;
	}
	/**
	 * 依据rule中的规则，挖掘url指定页面中的有效内容。包括内容标题、
	 * 内容来源、内容摘要、内容发布日期、内容正文。
	 * @param url 页面链接，指定预挖掘的页面
	 * @return 返回一个Map，Map中分别存储了内容来源、内容标题、
	 * 内容摘要、内容发布日期、内容正文。
	 * @throws Exception
	 */
	protected HashMap<String, Object> digTarget(String url) throws Exception {

		if ( url == null ) throw new Exception("url为空");		
		
		String page = this.grabPage(url);
		
		ArrayList<String> text = this.digText(page);
		if ( text == null ) throw new Exception(url+" 有效内容正文节点不存在");
		
		String title = this.digTitle(page);
		if ( title == null ) throw new Exception(url+" 有效内容标题节点不存在");
		
		String author = this.digAuthor(page);
		if ( author == null ) throw new Exception(url+" 有效内容作者节点不存在");

		String date = this.digDate(page);
		if ( date == null ) throw new Exception(url+" 有效内容日期节点不存在");
		
		String abstracts = this.digAbstract(page);
		if ( abstracts == null ) throw new Exception(url+" 有效内容摘要节点不存在");

		HashMap<String ,Object> result = new HashMap<String, Object>(8);
		result.put(DiggerTemplate.TITLE, title);
		result.put(DiggerTemplate.AUTHOR, author);
		result.put(DiggerTemplate.ABSTRACT, abstracts);
		result.put(DiggerTemplate.DATE, date);
		result.put(DiggerTemplate.TEXT, text);
		result.put(DiggerTemplate.FROM, url);
		
		return result;
	}
	/**
	 * 依据rule中的规则，挖掘page中的内容标题。
	 * @param page 存储了页面内容
	 * @return 返回一个字符串，表示挖掘出的内容标题
	 * @throws Exception
	 */
	protected abstract String digTitle(String page) throws Exception;
	/**
	 * 依据rule中的规则，挖掘page中的内容作者。
	 * @param page 存储了页面内容
	 * @return 返回一个字符串，表示挖掘出的内容作者
	 * @throws Exception
	 */
	protected abstract String digAuthor(String page) throws Exception;
	/**
	 * 依据rule中的规则，挖掘page中的内容发布日期。
	 * @param page 存储了页面内容
	 * @return 返回一个字符串，表示挖掘出的内容发布日期
	 * @throws Exception
	 */
	protected abstract String digDate(String page) throws Exception;
	/**
	 * 依据rule中的规则，挖掘page中的内容摘要。
	 * @param page 存储了页面内容
	 * @return 返回一个字符串，表示挖掘出的内容摘要
	 * @throws Exception
	 */
	protected abstract String digAbstract(String page) throws Exception;
	/**
	 * 依据rule中的规则，挖掘page中的内容正文。
	 * @param page 存储了页面内容
	 * @return 返回一个字符串，表示挖掘出的内容正文
	 * @throws Exception
	 */
	protected abstract ArrayList<String> digText(String page) throws Exception;
	/**
	 * 把从页面上抓取的内容，构造成一个Corpus对象。
	 * @param content 页面上抓取的内容存储在content中。
	 * @return 返回一个Corpus对象
	 * @throws Exception
	 */
	protected Corpus buildCorpus(HashMap<String, Object> content) throws Exception {
		if ( content == null ) throw new Exception("构建语料的内容为空");
		Corpus corpus = new Corpus();
		corpus.setSource((String) content.get(DiggerTemplate.FROM));
		corpus.setTitle((String) content.get(DiggerTemplate.TITLE));
		corpus.setAbstracts((String) content.get(DiggerTemplate.ABSTRACT));
		corpus.setDate((String) content.get(DiggerTemplate.DATE));
		corpus.setAuthor((String) content.get(DiggerTemplate.AUTHOR));
		corpus.setText((ArrayList<String>) content.get(DiggerTemplate.TEXT));
		return corpus;	
	}
	/**
	 * 对语料的内容进行修正。比如，去除<>标签及括起的内容、html实体替换等。
	 * @param c 待修正的语料
	 * @return 返回修正好的语料
	 * @throws Exception
	 */
	protected abstract Corpus fixCorpus(Corpus c) throws Exception;
	/**
	 * 把站点中有效链接中的内容转换成语料对象
	 * @return 返回若干语料对象
	 * @throws Exception
	 */
	public ArrayList<Corpus> dig() throws Exception {
		ArrayList<Corpus> corpuz = new ArrayList<Corpus>(128);
		
		HashSet<String> validUrls = validUrls(rule);		
		for (Iterator<String> itr=validUrls.iterator(); itr.hasNext();) {
			String validUrl = itr.next();
			HashMap<String, Object> target = null;
			try {
				target = digTarget(validUrl);
			} catch (Exception e) {
				LoggerFactory.SpiderLogger.trace("", e);
				target = null;
			}
			if ( target == null ) continue;
			
			Corpus corpus = null;
			try {
				corpus = buildCorpus(target);
			} catch (Exception e) {
				LoggerFactory.SpiderLogger.trace("", e);
				corpus = null;
			}
			if ( corpus == null ) continue;
			
			try {
				fixCorpus(corpus);
			} catch ( Exception e ) {
				LoggerFactory.SpiderLogger.trace("", e);
				continue;
			}
			
			corpuz.add(corpus);
		}		
		return corpuz;
	}
}
